<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>粒子时钟</title>
    <link href="https://fonts.googleapis.com/css2?family=DS+Digital&display=swap" rel="stylesheet">
</head>
<body>
<!--    绘制画布-->
<canvas></canvas>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    canvas{
        /*颜色渐变*/
        background: radial-gradient(#fff,#8c738c);
        display: block;
        width: 100vw;
        height: 100vh;
    }
</style>
<script>
    // 获取canvas容器
    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d',{
        // 用于告知浏览器或渲染引擎该元素或资源将被频繁读取。这可以帮助优化性能，减少重复计算或访问的开销。
        willReadFrequently: true,
    });
    // 获取canvas宽高
    function initCanvasSize(){
        // devicePixelRatio设备像素比率，保持清晰度
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }
    // 获取范围内随机数
    function  getRandom(min,max){
        return Math.floor(Math.random()*(max+1-min)+min)
    }
    // 创建粒子对象
    class Particle{
        constructor(){
            // 粒子半径   size
            // 获取粒子半径为2-7的随机数
            this.size = getRandom(2,7)
            // 初始大环半径  宽高最小值/2
            const r = Math.min(canvas.width,canvas.height)/2;
            // 粒子坐标点 根据大环角度随机生成于大环上
            // 角度-->随机角度-->弧度
            const rad = (getRandom(0,360)*Math.PI)/180;
            // 大环圆心坐标
            const cx = canvas.width/2;
            const cy = canvas.height/2;
            // 根据弧度计算粒子坐标  x,y
            this.x = cx+r*Math.cos(rad);
            this.y = cy+r*Math.sin(rad);
        }

        // 绘制粒子
        draw(){
            // 开启路径
            ctx.beginPath();
            // 绘制圆
            ctx.arc(this.x,this.y,this.size,0,Math.PI*2);
            // 填充颜色
            ctx.fillStyle= '#5445544d';
            // ctx.fillStyle='#000'
            ctx.fill();
        }

        // 粒子移动-->tx,ty为粒子要移动到的坐标点
        moveTo(tx,ty){
            // 定义动画过度时间
            const duration = 500;
            // 设置起始坐标
            const sx = this.x;
            const sy = this.y;
            // 定义x,y移动速度
            const speedX = (tx-sx)/duration;
            const speedY = (ty-sy)/duration;
            // 定义动画开始时间
            const startTime = Date.now();
            // 定义函数每次调用运动一点
            const _move = ()=>{
                // 当前运动时间
                const t = Date.now()-startTime;
                // 当前粒子坐标
                const x = sx+speedX*t;
                const y = sy+speedY*t;
                // 更新粒子坐标
                this.x = x;
                this.y = y;
                // 时间超过目标时间则停止运动
                if(t>=duration){
                    // 粒子坐标为目标坐标位置
                    this.x = tx;
                    this.y = ty;
                    // 终止
                    return;
                }
                requestAnimationFrame(_move)
            }
            _move()
        }
    }

    // 创建数组保存多个粒子的移动
    const particles = [];

    // 创建粒子
    // const p = new Particle();
    // 清空画布
    function clear(){
        ctx.clearRect(0,0,canvas.width,canvas.height);
    }
    // 清空画布重新绘制粒子
    function draw(){
        clear()
        // 更新粒子信息-->粒子数量，粒子坐标
        update()
        // 绘制
        for(const p of particles){
            p.draw()
        }
        // 用于请求浏览器在下一次重绘之前调用指定的回调函数 draw。这可以确保动画与显示器刷新率同步，提高性能和流畅度。
        requestAnimationFrame(draw)
    }
    // 避免文字的频繁更新
    let text = null;
    initCanvasSize()
    draw()

    // 获取要绘制的文字
    function getText(){
        // 返回当前时间的时分秒字符串，格式为HH:MM:SS。
        return new Date().toTimeString().substring(0,8)
    }




    function update(){
        // 画文字
        // 获取当前文字
        const curText= getText();
        // 判断文字是否变化
        if (text === curText){
            return;
        }
        // clear()
        // 有变化则更新文字
        text = curText;
        // 绘制文字
        const {width,height} = canvas;
        ctx.fillStyle = '#000'
        // 文字纵向对齐方式
        ctx.textBaseline = 'middle'
        ctx.font = `${100}px 'DS-Digital',sans-serif`;
        // 文字横向对齐方式
        ctx.textAlign = 'center';
        ctx.fillText(text,width/2,height/2);

        // 获取所有黑色像素点
        const points =  getPoints()
        // clear()
        for(let i =0;i<points.length;i++){
            const [x,y] = points[i];
            let p = particles[i]
            if(!p){
                p = new Particle();
                particles.push(p)
            }
            p.moveTo(x,y)
        }
        // 调整粒子数量以匹配像素点数量
        while (particles.length > points.length) {
            particles.pop();
        }
        // console.log(points.length)
    }
    function getPoints(){
        const points = [];
        // 获取像素信息
        const {data} = ctx.getImageData(0,0,canvas.width,canvas.height);
        // console.log(data)
        const gap = 1
        for(let i =0;i<canvas.width;i+=gap){
            for (let j = 0;j<canvas.height;j+=gap){
                const index = (i+j*canvas.width)*4;
                const r = data[index];
                const g = data[index+1];
                const b = data[index+2];
                const a = data[index+3];
                if(r===0&&g===0&&b===0&&a===255){
                    points.push([i,j])
                }
            }
        }
        return points;
    }


    // 创建粒子
    // for(let i=0;i<100;i++){
    //     const p = new Particle();
    //     p.draw()
    // }



</script>

</body>
</html>